﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Xml;
using Microsoft.Xna.Framework.Content;
using System.IO;
using System.Text.RegularExpressions;
using ThiefRun.Character;

namespace ThiefRun.Environment
{
    public class Level
    {
        #region Constants
        // Constants
        const string TILES_DIR = "Tiles";
        #endregion Constants

        #region Properties
        // Properties
        private List<List<List<Tile>>> map;
        private Rectangle bounds;
        private int currentFloor;
        #endregion Properties

        #region Computed Properties
        // Computed Properties
        public Rectangle Bounds { get { return bounds; } }
        public int CurrentFloor { get { return currentFloor; } }
        public List<List<List<Tile>>> Map { get { return map; } }
        #endregion

        #region Methods
        // Methods
        #region Public Methods
        #region Constructors
        /// <summary>
        /// Initialize the Level object.
        /// </summary>
        /// <param name="name">The level that will be loaded.</param>
        /// <param name="size">Provides the components of the size of the tile.</param>
        public Level(ContentManager content, string directory, Rectangle bounds)
        {
            this.map = new List<List<List<Tile>>>();
            this.bounds = bounds;

            Load(content, directory);
        }
        #endregion Constructors

        /// <summary>
        /// Draw the tile on the screen.
        /// </summary>
        /// <param name="gameTime">Provides access to the timer.</param>
        /// <param name="spriteBatch">Provides an object to a sprite.</param>
        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            map[currentFloor].ForEach(r => r.ForEach(t => t.Draw(gameTime, spriteBatch)));
        }

        /// <summary>
        /// Load all the levels.
        /// </summary>
        /// <param name="directory">The levels directory.</param>
        public void Load(ContentManager content, string directory)
        {
            List<string> files = Directory.EnumerateFiles(directory).ToList();
            Regex xmlRegex = new Regex("xml");
            
            foreach (string file in files)
            {
                // Only the load if it is an XML file.
                if (xmlRegex.IsMatch(file))
                    LoadLevel(content, file);
            }

            currentFloor = 0;
        }

        /// <summary>
        /// Handle the collisions with level
        /// </summary>
        /// <param name="player">The player to check.</param>
        public void Collides(Player player)
        {
            foreach (List<Tile> tileRow in map[currentFloor])
            {
                foreach (Tile tile in tileRow)
                {
                    // The tile was collided with.
                    if (tile.Collides(player))
                    {
                        currentFloor += tile.LevelChange;
                        player.Floor += tile.LevelChange;
                    }
                }
            }
        }
        #endregion Public Methods

        #region Private Methods
        /// <summary>
        /// Load the Level.
        /// </summary>
        /// <param name="name">The level that will be loaded.</param>
        private void LoadLevel(ContentManager content, string name)
        {
            XmlDocument xmlDoc;
            Dictionary<string, Dictionary<string, string>> dict;
            int floor = 0;
                        
            xmlDoc = new XmlDocument();
            xmlDoc.Load(name);

            // Process the different types of tiles.
            dict = LoadTileTypes(xmlDoc);

            // Load each floor.
            XmlNodeList floors = xmlDoc.GetElementsByTagName("Floor");

            foreach (XmlNode floorRow in floors)
            {
                LoadFloor(floorRow.ChildNodes, dict, content, floor);
                floor++;
            }
        }

         /// <summary>
        /// Load the Tile Types.
        /// </summary>
        /// <param name="xmlDoc">The XML document being parsed for the level.</param>
        private Dictionary<string, Dictionary<string, string>> LoadTileTypes(XmlDocument xmlDoc)
        {
            string id;
            Dictionary<string, Dictionary<string, string>> dict = new Dictionary<string, Dictionary<string, string>>();
            Dictionary<string, string> subDict;

            // Process the different types of tiles.
            XmlNodeList tileTypes = xmlDoc.GetElementsByTagName("TileType");
            foreach (XmlNode tileType in tileTypes)
            {
                id = "";
                subDict = new Dictionary<string, string>();
                foreach (XmlNode child in tileType.ChildNodes)
                {
                    switch (child.Name)
                    {
                        case "ID":
                            id = child.InnerText;
                            break;
                        default:
                            subDict.Add(child.Name, child.InnerText);
                            break;
                    }
                }
                // Ignore the empty id.
                if (id != "")
                    dict.Add(id, subDict);
            }
            return dict;
        }

        /// <summary>
        /// Load the Tiles.
        /// </summary>
        /// <param name="xmlDoc">The XML document being parsed for the level.</param>
        /// <param name="dict">The Tile Types for the level.</param>
        /// <param name="content">The ContentManager used to load the texture.</param>
        private void LoadFloor(XmlNodeList tiles, Dictionary<string, Dictionary<string, string>> dict, ContentManager content, int floor)
        {
            List<List<Dictionary<string, string>>> tempTiles = new List<List<Dictionary<string, string>>>();

            tempTiles = PreProcessTiles(tiles, dict);
            ProcessTiles(tempTiles, content, floor);
        }

        /// <summary>
        /// Preprocess the tiles.
        /// </summary>
        /// <param name="tiles">The list of XML tiles.</param>
        /// <param name="dict">The Tile Types for the level.</param>
        private List<List<Dictionary<string, string>>> PreProcessTiles(XmlNodeList tiles, Dictionary<string, Dictionary<string, string>> dict)
        {
            List<Dictionary<string, string>> row;
            Dictionary<string, string> type;
            List<List<Dictionary<string, string>>> tempTiles = new List<List<Dictionary<string, string>>>();

            foreach (XmlNode tileRow in tiles)
            {
                row = new List<Dictionary<string, string>>();
                foreach (XmlNode tile in tileRow.ChildNodes)
                {
                    foreach (XmlNode tileProperty in tile.ChildNodes)
                    {
                        switch (tileProperty.Name)
                        {
                            case "TileType":
                                type = dict[tileProperty.InnerText];
                                row.Add(type);
                                break;
                        }
                    }
                }
                tempTiles.Add(row);
            }
            return tempTiles;
        }

        /// <summary>
        /// Process the tiles.
        /// </summary>
        /// <param name="tempTiles">The temporary tiles</param>
        /// <param name="content">The content manager to load textures.</param>
        /// <param name="floor">The floor that the tile is on.</param>
        private void ProcessTiles(List<List<Dictionary<string, string>>> tempTiles, ContentManager content, int floor)
        {
            int max;
            List<Tile> row;
            Texture2D texture = null;
            Tile tileObj;
            Vector2 size;
            Vector2 pos;
            Tile.Passability passability = Tile.Passability.Passable;
            int levelChange = 0;

            // Calculate the desired size based on the existing tiles.
            max = tempTiles.Select(r => r.Count).Max();
            size = new Vector2(bounds.Width / max, bounds.Height / tempTiles.Count);

            pos = new Vector2(bounds.X, bounds.Y);
            map.Add(new List<List<Tile>>());
            // Process the tiles now that we have a size.
            foreach (List<Dictionary<string, string>> tileRow in tempTiles)
            {
                // row
                row = new List<Tile>();
                pos.X = bounds.X;
                foreach (Dictionary<string, string> tile in tileRow)
                {
                    levelChange = 0;
                    // tile
                    foreach (var pair in tile)
                    {
                        switch (pair.Key)
                        {
                            case "Texture":
                                texture = content.Load<Texture2D>(TILES_DIR + "/" + pair.Value);
                                break;
                            case "Passability":
                                switch (pair.Value)
                                {
                                    case "true":
                                        passability = Tile.Passability.Passable;
                                        break;
                                    case "false":
                                        passability = Tile.Passability.Impassable;
                                        break;
                                }
                                break;
                            case "LevelChange":
                                levelChange = Convert.ToInt32(pair.Value);
                                break;
                        }
                    }
                    tileObj = new Tile(pos, size, texture, passability, levelChange);
                    row.Add(tileObj);
                    pos.X += size.X;
                }
                this.map[floor].Add(row);
                pos.Y += size.Y;
            }
        }
        #endregion Private Methods
        #endregion Methods
    }
}
